﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-07-24 19:00
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using BassDrop.Core.Common.Cloud;
using BassDrop.Core.Common.Data;
using BassDrop.Core.Common.Data.IO;
using BassDrop.Core.Common.Data.IO.Portable;
using BassDrop.Core.Common.General.Collections;
using BassDrop.Core.Common.Localization;
using BassDrop.Core.Common.Media.Library;
using BassDrop.Core.Common.Media.Playlists;
using BassDrop.Core.Common.Options;
using BassDrop.Core.Common.Options.OptionsSets;
using BassDrop.Core.Common.Plugins;
using BassDrop.Core.Common.Services;

namespace BassDrop.Core.Common
{
	/// <summary>
	/// Main junction point of the application, provides a link between its
	/// components
	/// </summary>
	public class Controller : IDisposable
	{
		private bool m_loading;

		#region Properties

		/// <summary>
		/// Current instance of the controller
		/// </summary>
		public static Controller Instance
		{
			get;
			private set;
		}

		/// <summary>
		/// The name / ID of this application used eg. in xplf files
		/// </summary>
		public static string AppName
		{
			get
			{
				return "BassDrop";
			}
		}

		private static string s_machineId = "__unknown";

		/// <summary>
		/// The Id of this computer in the cloud
		/// </summary>
		public static string MachineId // TODO: ehelyett ahol csak lehet, locationID
		{
			get
			{
				return s_machineId;
			}
		}

		/// <summary>
		/// The working directory of the application
		/// </summary>
		public static Uri WorkingDir
		{
			get;
			private set;
		}

		/// <summary>
		/// The installation directory of the application
		/// </summary>
		public static Uri InstallDir
		{
			get;
			private set;
		}

		private Language m_applicationLanguage;

		/// <summary>
		/// The currently selected language of the application.
		/// </summary>
		public Language ApplicationLanguage
		{
			get
			{
				return m_applicationLanguage;
			}

			set
			{
				if ( value != m_applicationLanguage )
				{
					m_applicationLanguage = value;

					if ( ApplicationLanguageChanged != null )
					{
						ApplicationLanguageChanged( this, new PropertyChangedEventArgs( "ApplicationLanguage" ) );
					}
				}
			}
		}

		/// <summary>
		/// The settings of the application
		/// </summary>
		public ISettings ApplicationSettings
		{
			get;
			private set;
		}

		/// <summary>
		/// The options sets for the application
		/// </summary>
		public List<OptionsSetBase> ProgramOptions
		{
			get;
			private set;
		}

		/// <summary>
		/// Plugin manager object for the application
		/// </summary>
		public PluginManagerBase PluginManager
		{
			get;
			private set;
		}

		/// <summary>
		/// The currently played playlist
		/// </summary>
		private PlaylistFile m_nowPlayingFile;
		/// <summary>
		/// The currently played playlist
		/// </summary>
		public Playlist NowPlaying
		{
			get
			{
				return m_nowPlayingFile.RepresentedObject;
			}

			set
			{
				DoSetNowPlaying( value, false );
			}
		}

		#endregion Properties

		#region Events

		/// <summary>
		/// Event for changes to the NowPlaying property
		/// </summary>
		public event PropertyChangedEventHandler PlaylistChanged;

		/// <summary>
		/// Event for changes to the ApplicationLanguage property
		/// </summary>
		public event PropertyChangedEventHandler ApplicationLanguageChanged;

		#endregion Events

		/// <summary>
		/// Creates a new Controller object
		/// </summary>
		/// <param name="pluginManager">the plugin manager object for the application</param>
		/// <param name="applicationSettings">the settings of the application</param>
		private Controller( PluginManagerBase pluginManager, ISettings applicationSettings )
		{
			m_loading = true;

			ApplicationSettings = applicationSettings;
			PluginManager = pluginManager;
			ProgramOptions = new List<OptionsSetBase>();
		}

		/// <summary>
		/// Creates a new instance of the Controller and initializes its dependencies.
		/// Throws an exception if already initialized.
		/// Should be called as early as possible.
		/// </summary>
		/// <param name="installDir">absolute URI to the directory where the application is installed</param>
		/// <param name="workingDir">absolute URI to the working directory of the application</param>
		/// <param name="fileHandler">the file handler object for the application</param>
		/// <param name="pluginManager">the plugin manager object for the application</param>
		/// <param name="cryptoProvider">object to be used for encryption and decryption of data</param>
		/// <param name="webBrowser">object to be used for opening and interacting with web sites</param>
		/// <param name="userInterface">object to be used for showing various UI components to the user from the codebehind</param>
		/// <param name="applicationSettings">the settings of the application</param>
		public static void Init(
			Uri installDir,
			Uri workingDir,
			IFileHandler fileHandler,
			PluginManagerBase pluginManager,
			ICryptoProvider cryptoProvider,
			IWebBrowser webBrowser,
			IUserInterface userInterface,
			ISettings applicationSettings )
		{
			if ( Instance != null )
			{
				throw new InvalidOperationException( Strings.ExceptionControllerExists );
			}
			else
			{
				SuperCollectionStaticNonGeneric.SetDefaultSynchronizationContext();

				InstallDir = installDir;
				WorkingDir = workingDir;

				ServiceCenter.Init( fileHandler, cryptoProvider, webBrowser, userInterface );

				CreateClientId();

				CloudManager.Init();

				Instance = new Controller( pluginManager, applicationSettings );
			}
		}

		/// <summary>
		/// Reads or creates a unique GUID for this computer
		/// </summary>
		private static void CreateClientId()
		{
			string clientIdFilePath = Registry.ToFullPath( "Machine.id", Registry.PathKind.LocalSystem );

			if ( ServiceCenter.FileHandler.Exists( clientIdFilePath ) )
			{
				s_machineId = ServiceCenter.FileHandler.ReadAllText( clientIdFilePath );
			}
			else
			{
				s_machineId = Guid.NewGuid().ToString();
				ServiceCenter.FileHandler.WriteAllText( clientIdFilePath, s_machineId );
			}
		}

		/// <summary>
		/// Initializes the media player
		/// </summary>
		private void InitPlayer()
		{
			// Load current playlist
			DoSetNowPlaying( null, true );
		}

		/// <summary>
		/// Finish initializing the controller.
		/// Need to be called at last, after everything else is initialized
		/// </summary>
		public void FinishInit()
		{
			// Load options
			var generalOptions = new GeneralOptionsSet();
			generalOptions.Load( ApplicationSettings );
			ProgramOptions.Add( generalOptions );

			// Init library
			Library.Init();

			// Init player
			Controller.Instance.InitPlayer();

			m_loading = false;
		}

		/// <summary>
		/// Sets the value of the NowPlaying property
		/// </summary>
		/// <param name="value"></param>
		/// <param name="inner">if true, no checks and implicit corrections will be used</param>
		private void DoSetNowPlaying( Playlist value, bool inner )
		{
			if ( !inner && m_nowPlayingFile != null && value == m_nowPlayingFile.RepresentedObject )
			{
				return;
			}

			if ( m_nowPlayingFile != null )
			{
				m_nowPlayingFile.Dispose();
			}

			m_nowPlayingFile = new PlaylistFile(
				inner ? value : value ?? new Playlist( "Now playing" ), // replace null with empty playlist for requests from the outside
				ServiceCenter.FileHandler.ToUri( Registry.ToFullPath( Registry.CurrentPlaylistPath, Registry.PathKind.LocalUser ) ),
				Registry.ToFullPath( Registry.CurrentPlaylistPath, Registry.PathKind.RemoteClient ),
				BassDrop.Core.Common.Cloud.DefaultCloudConnectionProxy.Instance,
				true,
				true );

			if ( m_nowPlayingFile == null || m_nowPlayingFile.RepresentedObject == null )
			{
				System.Diagnostics.Debug.Assert( false, "The NowPlaying playlist set in the Controller is wrong, replacing it with an empty one" );
				m_nowPlayingFile = new PlaylistFile(
					new Playlist( "Now playing" ),
					ServiceCenter.FileHandler.ToUri( Registry.ToFullPath( Registry.CurrentPlaylistPath, Registry.PathKind.LocalUser ) ),
					Registry.ToFullPath( Registry.CurrentPlaylistPath, Registry.PathKind.RemoteClient ),
					BassDrop.Core.Common.Cloud.DefaultCloudConnectionProxy.Instance,
					true,
					true );
			}

			if ( PlaylistChanged != null )
			{
				PlaylistChanged( this, new PropertyChangedEventArgs( "NowPlaying" ) );
			}
		}

		/// <summary>
		/// Save the settings of the application
		/// </summary>
		private void SaveOptions()
		{
			if ( !m_loading )
			{
				foreach ( var option in ProgramOptions )
				{
					option.Save( ApplicationSettings );
				}

				ApplicationSettings.Save();
			}
		}

		#region IDisposable Members

		/// <summary>
		/// Frees the resources held by this object
		/// </summary>
		public void Dispose()
		{
			if ( m_nowPlayingFile != null )
			{
				m_nowPlayingFile.Dispose();
			}

			PluginManager.Dispose();
			// TODO: további dispose-ok
		}

		#endregion IDisposable Members

		/// <summary>
		/// Destroys the singleton instance of the class and removes it.
		/// To use it again, you have to reinitialize it.
		/// </summary>
		public static async Task Destroy()
		{
			Instance.SaveOptions();
			if ( Instance.NowPlaying != null )
			{
				Instance.NowPlaying.Stop();
			}
			await CloudManager.Destroy( false ); // TODO: jó a puha destroy?
			Instance.Dispose();
			Instance = null;
		}
	}
}
